package com.shareware.common.secure.keystore

import android.annotation.TargetApi
import android.os.Build
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import androidx.annotation.RequiresApi
import com.shareware.common.utils.hexToByteArray
import com.shareware.common.utils.log
import com.shareware.common.utils.toHexString
import java.security.GeneralSecurityException
import java.security.KeyStore
import java.util.*
import javax.crypto.Cipher
import javax.crypto.KeyGenerator
import javax.crypto.SecretKey
import javax.crypto.spec.GCMParameterSpec

/**
 * desc: 功能描述
 * email: mobiledeveloper@qq.com
 */
class AesGcmKS private constructor() {

    companion object {
        private const val ALGORITHM_NAME = "AndroidKeyStore"
        private const val TRANSFORMATION = "AES/GCM/NoPadding"
        private const val TAG = "AesGcm"

        //AES密钥长度为128bit、192bit、256bit，默认为128bit，一个字节8位
        private const val KEYSTORE_SIZE = 256

        //使用随机数产生的初始化向量才能达到语义安全
        private const val IV_GCM_DEFAULT__SIZE = 12

        @RequiresApi(api = Build.VERSION_CODES.M)
        fun encrypt(content: String, key: String): String {
            return if (content.isNotEmpty() && key.isNotEmpty()) {
                encrypt(content.toByteArray(), key)
            } else {
                log(TAG, "content or alias is null")
                ""
            }
        }

        @RequiresApi(api = Build.VERSION_CODES.M)
        fun encrypt(contentBytes: ByteArray, key: String): String {
            return if (contentBytes.isNotEmpty() && key.isNotEmpty()) {
                try {
                    val cipher = Cipher.getInstance(TRANSFORMATION)
                    val secretKey = generateSecretKey(key)
                    if (secretKey == null) {
                        log(TAG, "secret key is null")
                        return ""
                    }
                    cipher.init(Cipher.ENCRYPT_MODE, secretKey)
                    val encryptBytes = cipher.doFinal(contentBytes)
                    val ivBytes = cipher.iv
                    if (ivBytes == null || ivBytes.size != IV_GCM_DEFAULT__SIZE) {
                        log(TAG, "iv is invalid")
                        return ""
                    }
                    val result = Arrays.copyOf(ivBytes, ivBytes.size + encryptBytes.size)
                    System.arraycopy(encryptBytes, 0, result, ivBytes.size, encryptBytes.size)
                    result.toHexString()
                } catch (ex: GeneralSecurityException) {
                    ""
                }
            } else {
                ""
            }
        }

        @Synchronized
        @TargetApi(Build.VERSION_CODES.M)
        private fun generateSecretKey(keyString: String): SecretKey? {
            return try {
                val keyStore = KeyStore.getInstance(ALGORITHM_NAME)
                keyStore.load(null)
                val key = keyStore.getKey(keyString, null)
                if (key != null && key is SecretKey) {
                    key
                } else {
                    val keyGenerator = KeyGenerator.getInstance("AES", ALGORITHM_NAME)
                    val params = KeyGenParameterSpec.Builder(keyString, (KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT))
                            .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
                            .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
                            .setKeySize(KEYSTORE_SIZE)
                            .build()
                    keyGenerator.init(params)
                    keyGenerator.generateKey()
                }
            } catch (ex: Exception) {
                log(TAG, "${ex.message}")
                null
            }
        }

        @RequiresApi(api = Build.VERSION_CODES.M)
        fun decrypt(source: String, key: String): String {
            return if (source.isNotEmpty() && key.isNotEmpty()) {
                decrypt(source.hexToByteArray(), key)
            } else {
                log(TAG, "param is null")
                ""
            }
        }

        @RequiresApi(api = Build.VERSION_CODES.M)
        fun decrypt(dataBytes: ByteArray, key: String): String {
            return if (dataBytes.size > IV_GCM_DEFAULT__SIZE && key.isNotEmpty()) {
                val secretKey = generateSecretKey(key)
                if (secretKey == null) {
                    log(TAG, "secret key is null")
                    ""
                } else {
                    val ivBytes = dataBytes.copyOf(IV_GCM_DEFAULT__SIZE)
                    try {
                        val cipher = Cipher.getInstance(TRANSFORMATION)
                        val gcmParameterSpec = GCMParameterSpec(16 * 8, ivBytes)
                        cipher.init(Cipher.DECRYPT_MODE, secretKey, gcmParameterSpec)
                        cipher.doFinal(dataBytes, IV_GCM_DEFAULT__SIZE, dataBytes.size - IV_GCM_DEFAULT__SIZE).toString(Charsets.UTF_8)
                    } catch (ex: Exception) {
                        log(TAG, "GCM decrypt data exception: ${ex.message}")
                        ""
                    }
                }

            } else {
                log(TAG, "param is invalid")
                ""
            }
        }
    }
}